package geppetto.cat.alignments;

import geppetto.cat.corpus.BilingualCorpus;
import gnu.trove.TIntArrayList;

import java.io.IOException;
import java.util.ArrayList;



public class AlignmentEvaluator {

	
	/**
	 * Creates the oracle prediction for 1-n aligments
	 * For each foreing position if the gold has a sure point than that point is added. If the gold does not have
	 * a sure point but has a possible point is hadded otherwise no point is added. Precision should 100 
	 * @param goldSet
	 * @return
	 */
	public static AlignmentsSet getOraclePredictions(AlignmentsSet goldSet){
		AlignmentsSet oracleSet = new AlignmentsSet();
		for(Alignment gold : goldSet._alignments){
			Alignment oracle = new Alignment(gold._sentenceNumber,gold._sentenceSource,gold._sourceLen,gold._foreignLen);
			for(int i = 0; i < oracle._foreignLen; i++){
				TIntArrayList pos = gold.getSurePosition(i);
				if(pos.size() > 0){
					oracle.add(pos.get(0), i);
				}else{
					pos = gold.getPossiblePosition(i);
					if(pos.size() > 0){
					oracle.add(pos.get(0), i);
					
					}
				}
			}
			oracleSet.addAlignment(oracle);
		}
		return oracleSet;
	}
	
	
	public static float[] calculateMeasures(AlignmentsSet predicted,
			AlignmentsSet gold) {
		return calculateMeasures(predicted.getAlignments(), gold
				.getAlignments());
	}

	// 0 sure emtpy, 1 possible emptu, 2 empty
	public static int[] calculateUnaligend(Alignment predicted, Alignment gold) {
		int[] res = new int[3];
		for (int foreign = 0; foreign < predicted.getForeignLenght(); foreign++) {
			boolean isSure = false;
			boolean isPossible = false;
			boolean hasposition = false;
			for (int source = 0; source < predicted.getSourceLenght(); source++) {
				if (gold.isSure(source, foreign)) {
					isSure = true;
				} else if (gold.isPossible(source, foreign)) {
					isPossible = true;
				}
				if (predicted.hasPosition(source, foreign))
					hasposition = true;
			}
			if (!hasposition) {
				if (isSure) {
					res[0]++;
				} else if (isPossible) {
					res[1]++;
				} else {
					res[2]++;
				}
			}
		}
		// System.out.println(res[0]+ " " +res[1]+" "+res[2]);
		return res;
	}

	/**
	 * Return two evaluation. Position 0 is evaluation for rare workds position 1 is evaluation for non-rare words
	 * @param predicted
	 * @param gold
	 * @param corpus
	 * @return
	 */
	public static Evaluation[] evaluateRareWords(AlignmentsSet predicted,
			AlignmentsSet gold, BilingualCorpus corpus) {
		Evaluation[] total = { new Evaluation(0, 0, 0, 0, 0, 0, 0),
				new Evaluation(0, 0, 0, 0, 0, 0, 0) };
		if (predicted.size() != gold.size())
			throw new IlegalAlignmentDimensions("gold:" + gold.size()
					+ " predicted:" + predicted.size());
		int size = predicted.size();
		for (int i = 0; i < size; i++) {
			Evaluation[] local = evaluateRareWords(predicted.get(i), gold
					.get(i), corpus);
			total[0].add(local[0]);
			total[1].add(local[1]);

		}
		return total;

	}

	public static Evaluation[] evaluateFertilityWords(AlignmentsSet predicted,
			AlignmentsSet gold, BilingualCorpus corpus) {
		Evaluation[] total = { new Evaluation(0, 0, 0, 0, 0, 0, 0),
				new Evaluation(0, 0, 0, 0, 0, 0, 0) };
		if (predicted.size() != gold.size())
			throw new IlegalAlignmentDimensions("gold:" + gold.size()
					+ " predicted:" + predicted.size());
		int size = predicted.size();
		for (int i = 0; i < size; i++) {
			Evaluation[] local = evaluateFertilityWords(predicted.get(i), gold
					.get(i), corpus);
			total[0].add(local[0]);
			total[1].add(local[1]);

		}
		return total;

	}

	public static Evaluation evaluate(AlignmentsSet predicted,
			AlignmentsSet gold) {
		Evaluation total = new Evaluation(0, 0, 0, 0, 0, 0, 0);
		if (predicted.size() != gold.size())
			throw new IlegalAlignmentDimensions("gold:" + gold.size()
					+ " predicted:" + predicted.size());
		int size = predicted.size();
		for (int i = 0; i < size; i++) {
			total.add(evaluate(predicted.get(i), gold.get(i)));

		}
		return total;
	}

	public static float[] calculateMeasures(ArrayList<Alignment> predicted,
			ArrayList<Alignment> gold) {
		Evaluation total = new Evaluation(0, 0, 0, 0, 0, 0, 0);
		if (predicted.size() != gold.size())
			throw new IlegalAlignmentDimensions();
		int size = predicted.size();
		for (int i = 0; i < size; i++) {
			total.add(evaluate(predicted.get(i), gold.get(i)));

		}
		return new float[] { total.getPrecision(), total.getRecall(),
				total.getAER(), total.ans, total.anp, total.a, total.s };
	}

	public static Evaluation evaluate(Alignment predicted, Alignment gold) {
		if (predicted.getForeignLenght() != gold.getForeignLenght()
				|| predicted.getSourceLenght() != gold.getSourceLenght())
			throw new IlegalAlignmentDimensions("p.n "
					+ predicted.getSentenceNumber() + "g.n"
					+ gold.getSentenceNumber() + "\np.f="
					+ predicted.getForeignLenght() + " p.s="
					+ predicted.getSourceLenght() + "\ng.f="
					+ gold.getForeignLenght() + " g.s="
					+ gold.getSourceLenght());
		int sure = 0, alignedAndSure = 0, alignedAndPossible = 0, totalAligned = 0;
		for (int foreign = 0; foreign < predicted.getForeignLenght(); foreign++) {
			for (int source = 0; source < predicted.getSourceLenght(); source++) {
				if (gold.isSure(source, foreign)) {
					sure++;
					if (predicted.hasPosition(source, foreign)) {
						alignedAndSure++;
						alignedAndPossible++;
					}
				}
				if (gold.isPossible(source, foreign)) {
					if (predicted.hasPosition(source, foreign)) {
						alignedAndPossible++;
					}
				}
				if (predicted.hasPosition(source, foreign))
					totalAligned++;
			}
		}
		int[] res = calculateUnaligend(predicted, gold);
		// argument order = (ans, anp, a, s)
		return new Evaluation(alignedAndSure, alignedAndPossible,
				totalAligned, sure, res[2], res[0], res[1]);
	}

	public static Evaluation[] evaluateRareWords(Alignment predicted,
			Alignment gold, BilingualCorpus corpus) {
		if (predicted.getForeignLenght() != gold.getForeignLenght()
				|| predicted.getSourceLenght() != gold.getSourceLenght())
			throw new IlegalAlignmentDimensions("p.f="
					+ predicted.getForeignLenght() + " p.s="
					+ predicted.getSourceLenght() + "\ng.f="
					+ gold.getForeignLenght() + " g.s="
					+ gold.getSourceLenght());
		int sure = 0, alignedAndSure = 0, alignedAndPossible = 0, totalAligned = 0;
		int sure2 = 0, alignedAndSure2 = 0, alignedAndPossible2 = 0, totalAligned2 = 0;
		for (int foreign = 0; foreign < predicted.getForeignLenght(); foreign++) {
			for (int source = 0; source < predicted.getSourceLenght(); source++) {
				if (corpus
						.sourceRareWord(corpus.getSourceWordId(gold
								.getSentenceNumber(), gold.getSentenceSource(),
								source))) {
					if (gold.isSure(source, foreign)) {
						sure++;
						if (predicted.hasPosition(source, foreign)) {
							alignedAndSure++;
							alignedAndPossible++;
						}
					}
					if (gold.isPossible(source, foreign)) {
						if (predicted.hasPosition(source, foreign)) {
							alignedAndPossible++;
						}
					}
					if (predicted.hasPosition(source, foreign))
						totalAligned++;
				} else {
					if (gold.isSure(source, foreign)) {
						sure2++;
						if (predicted.hasPosition(source, foreign)) {
							alignedAndSure2++;
							alignedAndPossible2++;
						}
					}
					if (gold.isPossible(source, foreign)) {
						if (predicted.hasPosition(source, foreign)) {
							alignedAndPossible2++;
						}
					}
					if (predicted.hasPosition(source, foreign))
						totalAligned2++;
				}
			}
		}
		// argument order = (ans, anp, a, s)
		Evaluation[] results = new Evaluation[2];
		results[0] = new Evaluation(alignedAndSure, alignedAndPossible,
				totalAligned, sure, 0, 0, 0);
		results[1] = new Evaluation(alignedAndSure2, alignedAndPossible2,
				totalAligned2, sure2, 0, 0, 0);
		return results;
	}

	public static Evaluation[] evaluateFertilityWords(Alignment predicted,
			Alignment gold, BilingualCorpus corpus) {
		if (predicted.getForeignLenght() != gold.getForeignLenght()
				|| predicted.getSourceLenght() != gold.getSourceLenght())
			throw new IlegalAlignmentDimensions("p.f="
					+ predicted.getForeignLenght() + " p.s="
					+ predicted.getSourceLenght() + "\ng.f="
					+ gold.getForeignLenght() + " g.s="
					+ gold.getSourceLenght());
		int sure = 0, alignedAndSure = 0, alignedAndPossible = 0, totalAligned = 0;
		int sure2 = 0, alignedAndSure2 = 0, alignedAndPossible2 = 0, totalAligned2 = 0;
		for (int foreign = 0; foreign < predicted.getForeignLenght(); foreign++) {
			for (int source = 0; source < predicted.getSourceLenght(); source++) {
				if (corpus.foreignHighFertilityWord(corpus.getForeignWordId(
						gold.getSentenceNumber(), gold.getSentenceSource(),
						foreign))) {
					if (gold.isSure(source, foreign)) {
						sure++;
						if (predicted.hasPosition(source, foreign)) {
							alignedAndSure++;
							alignedAndPossible++;
						}
					}
					if (gold.isPossible(source, foreign)) {
						if (predicted.hasPosition(source, foreign)) {
							alignedAndPossible++;
						}
					}
					if (predicted.hasPosition(source, foreign))
						totalAligned++;
				} else {
					if (gold.isSure(source, foreign)) {
						sure2++;
						if (predicted.hasPosition(source, foreign)) {
							alignedAndSure2++;
							alignedAndPossible2++;
						}
					}
					if (gold.isPossible(source, foreign)) {
						if (predicted.hasPosition(source, foreign)) {
							alignedAndPossible2++;
						}
					}
					if (predicted.hasPosition(source, foreign))
						totalAligned2++;
				}
			}
		}
		// argument order = (ans, anp, a, s)
		Evaluation[] results = new Evaluation[2];
		results[0] = new Evaluation(alignedAndSure, alignedAndPossible,
				totalAligned, sure, 0, 0, 0);
		results[1] = new Evaluation(alignedAndSure2, alignedAndPossible2,
				totalAligned2, sure2, 0, 0, 0);
		return results;
	}

	/**
	 * returns {precision, recall, AER}
	 */
	public static float[] calculateMeasuresPerSentence(Alignment predicted,
			Alignment gold) {
		Evaluation e = evaluate(predicted, gold);
		return new float[] { e.getPrecision(), e.getRecall(), e.getAER() };
	}

	public static class IlegalAlignmentDimensions extends Error {

		private static final long serialVersionUID = 1L;

		public IlegalAlignmentDimensions() {

		}

		public IlegalAlignmentDimensions(String text) {
			System.out.println("IllegalAlignmentDimensions: " + text);
		}
	}

	public static class Evaluation {
		// A = guessed alignments
		// S = Sure alignments
		// P = Possible alignments (superset of sure)
		/** |A ^ S| */
		public int ans;

		/** |A ^ P| */
		public int anp;

		/** |A| */
		public int a;

		/** |S| */
		public int s;

		// unaligned points means foreing words that were aligned to null
		// If the foreign word is unalinged in the gold
		public int unalignedEmpty;

		// If the foreing word is aligned to a sure point
		public int unalignedSure;

		// If the foreing word is aligned to a possible point
		public int unalignedPossible;

		public Evaluation(int ans, int anp, int a, int s, int unalignedE,
				int unalignedS, int unalignedP) {
			this.ans = ans;
			this.anp = anp;
			this.a = a;
			this.s = s;
			unalignedEmpty = unalignedE;
			unalignedSure = unalignedS;
			unalignedPossible = unalignedP;
		}

		public void add(Evaluation e) {
			this.ans += e.ans;
			this.anp += e.anp;
			this.a += e.a;
			this.s += e.s;
			this.unalignedEmpty += e.unalignedEmpty;
			this.unalignedPossible += e.unalignedPossible;
			this.unalignedSure += e.unalignedSure;
		}

		public float getPrecision() {
			return anp == 0 ? 0 : anp / (float) a;
		}

		public float getRecall() {
			return ans == 0 ? 0 : ans / (float) s;
		}

		public float getAER() {
			return anp + ans == 0 ? 1 : 1 - (ans + anp) / (float) (a + s);
		}

		public float getF1(double a) {
			float p = getPrecision();
			float r = getRecall();
			return (float) (1 / (a / p + (1 - a) / r));
		}

		public String toString() {
			return "P=" + prettyPrint(getPrecision() * 100, 2, 6) + " R="
					+ prettyPrint(getRecall() * 100, 2, 6) + " AER "
					+ prettyPrint(getAER() * 100, 2, 6) + " F1(0.3) "
					+ prettyPrint(getF1(0.3) * 100, 2, 6) + " F1(0.5) "
					+ prettyPrint(getF1(0.5) * 100, 2, 6) + " Unalinged Empty "
					+ unalignedEmpty + " Unalinged Sure " + unalignedSure
					+ " 	Unalinged Possible " + unalignedPossible + " Nr poits "
					+ a;
		}

		public String latexTableHeader() {
			return "&P=" + " &R=" + " &AER " + " &F1(0.3) " + " &F1(0.5) "
					+ " &unalignedE " + " &unalignedS " + " &unalignedP "
					+ " &Nr poits \\\\";
		}

		public String toLatexTableEntry() {
			return "&" + prettyPrint(getPrecision() * 100, 2, 6) + " &"
					+ prettyPrint(getRecall() * 100, 2, 6) + " & "
					+ prettyPrint(getAER() * 100, 2, 6) + " & "
					+ prettyPrint(getF1(0.3) * 100, 2, 6) + " & "
					+ prettyPrint(getF1(0.5) * 100, 2, 6) + " & "
					+ unalignedEmpty + " & " + unalignedSure + " & "
					+ unalignedPossible + " & " + a + "\\\\";
		}

		public String toSimpleString() {
			return prettyPrint(getPrecision() * 100, 2, 6) + "\t"
					+ prettyPrint(getRecall() * 100, 2, 6) + "\t"
					+ prettyPrint(getAER() * 100, 2, 6) + "\t"
					+ prettyPrint(getF1(0.3) * 100, 2, 6) + "\t"
					+ prettyPrint(getF1(0.5) * 100, 2, 6) + "\t"
					+ unalignedEmpty + "\t" + unalignedSure + "\t"
					+ unalignedPossible + "\t" + a;
		}

		// TODO move to common place
		private String prettyPrint(double d, int precision, int w) {
			java.text.DecimalFormat fmt = new java.text.DecimalFormat();
			fmt.setMaximumFractionDigits(precision);
			fmt.setMinimumFractionDigits(precision);
			fmt.setGroupingUsed(false);
			fmt.setMaximumIntegerDigits(4);
			String s = fmt.format(d);
			while (s.length() < w) {
				s = " " + s;
			}
			return s;
		}

	}

	// Return a int array. Each posistion is a word and the value is the mediam
	// fertility on the list of aligments received as arguments

	public static double[] calculateFertilities(ArrayList<Alignment> al,
			BilingualCorpus corpus, byte sentenceSource) {
		double[] fertility = new double[corpus.getForeignSize()];
		int[] counts = new int[corpus.getForeignSize()];
		for (Alignment a : al) {
			for (int i = 0; i < a.getForeignLenght(); i++) {
				for (int j = 0; j < a.getSourceLenght(); j++) {
					if (a.isSure(j, i)) {
						fertility[corpus.getForeignWordId(
								a.getSentenceNumber(), sentenceSource, i)]++;
					}

				}
				counts[corpus.getForeignWordId(a.getSentenceNumber(),
						sentenceSource, i)]++;
			}
		}
		for (int i = 0; i < corpus.getForeignSize(); i++) {
			if (counts[i] != 0) {
				fertility[i] = fertility[i] / counts[i];
			}
		}
		return fertility;
	}

	public static void main(String[] args) throws IOException {
		BilingualCorpus corpus = BilingualCorpus.getCorpusFromFileDescription(
				args[0], Integer.parseInt(args[1]));
		System.out.println("Forward oracle " + AlignmentEvaluator.evaluate(AlignmentEvaluator.getOraclePredictions(corpus.getGold()), corpus.getGold()));
		BilingualCorpus revCorpus = corpus.reverse();
		System.out.println("Bacwkard oracle " + AlignmentEvaluator.evaluate(AlignmentEvaluator.getOraclePredictions(revCorpus.getGold()), revCorpus.getGold()));
		System.out.println("Read corpus");
	}

}
